<!-- File: Card.vue (Vue 3 SFC, JavaScript, mobile-first) -->
<template>
  <component
    :is="href ? 'a' : 'div'"
    class="m-card"
    :href="href"
    :class="[
      `m-card--${size}`,
      { 'is-clickable': isClickable, 'is-pressed': pressed, 'is-loading': loading, 'no-shadow': !shadow, 'no-rounded': !rounded }
    ]"
    :style="{ '--aspect': aspect }"
    @click="onClick"
    @touchstart.passive="onTouchStart"
    @touchend.passive="onTouchEnd"
    @touchcancel.passive="onTouchEnd"
    role="group"
    :aria-busy="loading ? 'true' : 'false'"
  >
    <!-- Media / cover -->
    <div v-if="hasMedia" class="m-card__media">
      <slot name="media">
        <img
          v-if="imageSrc"
          class="m-card__img"
          :src="imageSrc"
          :alt="imageAlt"
          loading="lazy"
          decoding="async"
        />
      </slot>
      <div v-if="$slots.badge" class="m-card__badge">
        <slot name="badge" />
      </div>
    </div>

    <!-- Body -->
    <div class="m-card__body">
      <slot name="header">
        <h3 v-if="title" class="m-card__title">{{ title }}</h3>
        <p v-if="subtitle" class="m-card__subtitle">{{ subtitle }}</p>
      </slot>

      <div class="m-card__content">
        <slot />
      </div>

      <div v-if="$slots.actions" class="m-card__actions">
        <slot name="actions" />
      </div>
    </div>

    <!-- Lightweight skeleton overlay when loading -->
    <div v-if="loading" class="m-card__skeleton" aria-hidden="true" />
  </component>
</template>

<script setup lang="ts">
import { computed, ref, useSlots } from 'vue'

const props = defineProps({
  title: { type: String, default: '' },
  subtitle: { type: String, default: '' },
  imageSrc: { type: String, default: '' },
  imageAlt: { type: String, default: '' },
  href: { type: String, default: '' },
  clickable: { type: Boolean, default: false },
  loading: { type: Boolean, default: false },
  size: { type: String, default: 'md' }, // sm | md | lg
  shadow: { type: Boolean, default: true },
  rounded: { type: Boolean, default: true },
  aspect: { type: String, default: '16/9' }, // CSS aspect-ratio value
})

const emit = defineEmits(['click'])
const slots = useSlots()

const pressed = ref(false)
const isClickable = computed(() => props.clickable || !!props.href)
const hasMedia = computed(() => !!props.imageSrc || !!slots.media)

function onClick(e) {
  if (props.loading) {
    e.preventDefault()
    return
  }
  emit('click', e)
}

function onTouchStart() {
  if (isClickable.value) pressed.value = true
}
function onTouchEnd() {
  pressed.value = false
}
</script>

<style scoped>
/* Core */
.m-card {
  --radius: 16px;
  --pad: 12px;
  --shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
  --border: 1px solid rgba(0, 0, 0, 0.06);
  --aspect: 16/9;

  position: relative;
  display: block;
  background: #fff;
  border: var(--border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  width: 100%;

  margin: 0.5rem 0.45rem;
}
.m-card.no-shadow { box-shadow: none; }
.m-card:not(.no-shadow) { box-shadow: var(--shadow); }
.m-card.no-rounded { border-radius: 0; }

/* Click / press feedback tailored for mobile */
.m-card.is-clickable { cursor: pointer; }
.m-card.is-pressed { transform: scale(0.998); filter: brightness(0.98); }

/* Media */
.m-card__media {
  position: relative;
  aspect-ratio: var(--aspect);
  background: #f6f7f9;
}
.m-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.m-card__badge {
  position: absolute;
  top: 8px;
  left: 8px;
}

/* Body */
.m-card__body { padding: var(--pad); }
.m-card__title {
  margin: 0;
  font-size: clamp(16px, 3.8vw, 20px);
  line-height: 1.25;
  font-weight: 600;
}
.m-card__subtitle {
  margin: 4px 0 0;
  color: #666;
  font-size: 12px;
}
.m-card__content {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.5;
  color: #333;
}
.m-card__actions {
  margin-top: 12px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Sizes */
.m-card--sm { --pad: 10px; --radius: 12px; }
.m-card--lg { --pad: 16px; --radius: 20px; }

/* Skeleton shimmer overlay */
.m-card.is-loading * { pointer-events: none; }
.m-card__skeleton {
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgba(0,0,0,0.04) 20%, rgba(0,0,0,0.09) 30%, rgba(0,0,0,0.04) 40%);
  background-size: 200% 100%;
  animation: shimmer 1.2s infinite linear;
}
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

<!--
USAGE EXAMPLE (JS project)
-------------------------------------------------
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

// App.vue
<template>
  <div class="stack">
    <Card
      clickable
      image-src="https://picsum.photos/640/360"
      title="禅意清风"
      subtitle="古法新技 · 质感呈现"
      @click="() => console.log('card clicked')"
    >
      <p>支持插槽：header / media / actions / 默认。移动端手感做了按压反馈。</p>
      <template #actions>
        <button class="btn">了解更多</button>
        <button class="btn btn--ghost">收藏</button>
      </template>
    </Card>

    <Card size="sm" loading title="加载中…" />
  </div>
</template>

<script setup>
import Card from './Card.vue'
</script>

<style scoped>
.stack { display: grid; gap: 12px; padding: 12px; }
.btn { padding: 10px 14px; border-radius: 999px; border: none; background: #111; color: #fff; }
.btn.btn--ghost { background: #f0f0f0; color: #111; }
</style>
-->
